package com.centerm.bluetooth.ibridge.parse;

import com.centerm.bluetooth.model.Respond;
import com.centerm.bluetooth.model.RespondStatus;
import com.centerm.util.HexUtil;
import com.centerm.util.Logger;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import static com.centerm.bluetooth.model.Respond.NOTICE;
import static com.centerm.bluetooth.model.RespondStatus.ERROR_CMD_NO_FIT;
import static com.centerm.bluetooth.model.RespondStatus.ERROR_LASTER_FORMAT;
import static com.centerm.bluetooth.model.RespondStatus.ERROR_LEN;
import static com.centerm.bluetooth.model.RespondStatus.ERROR_NO_COMPLETION;
import static com.centerm.bluetooth.model.RespondStatus.ERROR_PACKAGER_ID;
import static com.centerm.bluetooth.model.RespondStatus.OK;

/**
 * Created by linwenhui on 2017/2/8.
 */

public enum PackageParse {
    //新大陆
    LKLFormat {
        final static int REQ = 0x2F;
        final static int NOTICE = 0x3F;
        final static int RESPOND = 0x4F;

        @Override
        public byte[] header() {
            return new byte[]{0x4C, 0x4B};
        }

        @Override
        public byte[] laster() {
            return new byte[]{0x03};
        }

        @Override
        public int getPackagePreLength() {
            return 2;
        }

        @Override
        public int getPackageLength(byte[] data) {
            if (data != null && data.length == getPackagePreLength())
                try {
                    return Integer.valueOf(HexUtil.bcd2Str(data)) + 2;
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                }
            return 0;
        }

        @Override
        public int corverType(int type) {
            switch (type) {
                case NOTICE:
                    return Respond.NOTICE;
                case REQ:
                    return Respond.REQ;
                default:
                    return Respond.RESPOND;
            }
        }

        @Override
        public byte[] requestPack(byte packageId, byte[] cmd, byte[] params) throws IOException {
            int paramLen = 0; //参数字节数
            if (params != null && params.length > 0)
                paramLen = params.length;
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            bos.write(header()); //包头
            bos.write(HexUtil.toLen(paramLen + 4)); //包长度,参数和指令的字节数，高字节优先输出
            bos.write((byte) REQ); //指示位
            bos.write(cmd); //包指令
            bos.write(packageId); //序列号
            if (paramLen > 0) {
                bos.write(params); //包参数
            }
            bos.write(laster());
            bos.write(verify(bos.toByteArray(), 2, 0));
            byte[] reqParams = bos.toByteArray();
            bos.close();
            return reqParams;
        }

        @Override
        public Respond respondPack(byte packageId, byte[] resultDatas, byte[] cmd) {
            if (resultDatas == null || resultDatas.length < 10) {
                return new Respond(ERROR_NO_COMPLETION);
            }
            byte[] headBytes = header();
            if ((resultDatas[0] & 0xFF) == (headBytes[0] & 0xFF) && (resultDatas[1] & 0xFF) == (headBytes[1] & 0xFF)) {
                String lenStr = HexUtil.bcd2Str(new byte[]{resultDatas[2], resultDatas[3]});
                int dataLen = 0;
                try {
                    dataLen = Integer.valueOf(lenStr);
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                }
                Logger.i("dataLen == " + dataLen + " ===  resultDatas === " + resultDatas.length);
                if (dataLen + 6 < resultDatas.length) {
                    return new Respond(ERROR_LEN);
                }
                byte verify = verify(resultDatas, 2, 1);
                Logger.i(" verify === " + verify + " , " + resultDatas[resultDatas.length - 1]);
                if (verify != resultDatas[resultDatas.length - 1]) {
                    return new Respond(RespondStatus.ERROR_VERIFY);
                }
                final int type = (resultDatas[4] & 0xFF);
                if (type == RESPOND) {//响应报文
                    final boolean cmdCheck = (((cmd[0] & 0xFF) == (resultDatas[5] & 0xFF)) && ((cmd[1] & 0xFF) == (resultDatas[6] & 0xFF)));//同一条指令的响应
                    if (cmdCheck) {
//                        if (packageId != resultDatas[7])
//                            return new Respond(ERROR_PACKAGER_ID);
                        return respondHandle(resultDatas, dataLen, Respond.RESPOND);
                    } else {
                        return new Respond(ERROR_CMD_NO_FIT).setCmd(new byte[]{resultDatas[5], resultDatas[6]}).setAllPackageData(resultDatas);
                    }
                } else {
                    return respondHandle(resultDatas, dataLen, corverType(type));
                }
            }
            return new Respond(RespondStatus.ERROR_HEAD_FORMAT);
        }

        private Respond respondHandle(byte[] resultDatas, int dataLen, int type) {
            //当包类型为成功应答才有必要去异或参数的字节数
            if (0x30 == (resultDatas[8] & 0xFF) && 0x30 == (resultDatas[9] & 0xFF)) {
                dataLen -= 6;
                if (dataLen > 0) {
                    byte[] buffer = new byte[dataLen];
                    for (int i = 0; i < dataLen; i++) {
                        buffer[i] = resultDatas[10 + i];
                    }
                    return new Respond(RespondStatus.OK, type, buffer).setCmd(new byte[]{resultDatas[5], resultDatas[6]}).setAllPackageData(resultDatas);
                }
                return new Respond(RespondStatus.OK, type).setCmd(new byte[]{resultDatas[5], resultDatas[6]}).setAllPackageData(resultDatas);
            } else {
                char[] codeBytes = new char[2];
                codeBytes[0] = (char) (resultDatas[8] & 0xFF);
                codeBytes[1] = (char) (resultDatas[9] & 0xFF);
                String codeStr = String.valueOf(codeBytes);
                int code = 0;
                try {
                    code = Integer.valueOf(codeStr);
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                }
                return new Respond(RespondStatus.valueOf(code, 0), type).setCmd(new byte[]{resultDatas[5], resultDatas[6]}).setAllPackageData(resultDatas);
            }
        }
    },
    //默认通用
    CentermFormat {
        final static int REQ = 0x51;//请求
        final static int RESPOND = 0x41;//响应正确
        final static int RESPOND_ERROR = 0x45;//响应错误

        @Override
        public byte[] header() {
            return new byte[]{0x55, (byte) 0xAA};
        }

        @Override
        public byte[] laster() {
            return new byte[]{(byte) 0xCC, 0x33};
        }

        @Override
        public int getPackagePreLength() {
            return 4;
        }

        @Override
        public int getPackageLength(byte[] data) {
            if (data != null && data.length == getPackagePreLength()) {
                int dataLen = (data[3] << 8 & 0xFF00)
                        | (data[2] & 0xFF);
                return dataLen + 3;
            }

            return 0;
        }

        @Override
        public int corverType(int type) {
            switch (type) {
                case NOTICE:
                    return NOTICE;
                case REQ:
                    return Respond.REQ;
                default:
                    return Respond.RESPOND;
            }
        }

        @Override
        public byte[] requestPack(byte packageId, byte[] cmd, byte[] params) throws IOException {
            int paramsLen = 0;//参数的字节数
            if (params != null && params.length > 0) {
                paramsLen = params.length;
            }
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            bos.write(header());//包起止符2字符
            bos.write(REQ);//包类型
            bos.write(packageId);//包序号
            Logger.i("【包序号】" + packageId);
            byte[] parasLenBytes = new byte[2];
            parasLenBytes[0] = (byte) ((paramsLen + 2) & 0xFF);
            parasLenBytes[1] = (byte) ((paramsLen + 2) >> 8 & 0xFF);
            bos.write(parasLenBytes);//包长度,参数和指令的字节数，低字节优先输出
            bos.write(cmd);//包指令
            //包参数
            if (paramsLen > 0)
                bos.write(params);
            bos.write(verify(bos.toByteArray(), 2, 0));
            bos.write(laster());//终止符
            byte[] reqParams = bos.toByteArray();
            bos.close();
            return reqParams;
        }

        @Override
        public Respond respondPack(byte packageId, byte[] resultDatas, byte[] cmd) {
            if (resultDatas == null || resultDatas.length < 10) {
                return new Respond(ERROR_NO_COMPLETION).setCmd(new byte[]{resultDatas[6], resultDatas[7]});
            }
            //起始符
            final byte[] headBytes = header();
            if ((resultDatas[0] & 0xFF) == (headBytes[0] & 0xFF) && (resultDatas[1] & 0xFF) == (headBytes[1] & 0xFF)) {
                //结束符
                final byte[] lasterBytes = laster();
                final boolean endFlag = (byte) (resultDatas[resultDatas.length - 2] & 0xFF) == (byte) (lasterBytes[0] & 0xFF)
                        && (byte) (resultDatas[resultDatas.length - 1] & 0xFF) == (byte) (lasterBytes[1] & 0xFF);
                Logger.i("【解析报文尾】" + endFlag);
                if (!endFlag) {
                    return new Respond(ERROR_LASTER_FORMAT).setCmd(new byte[]{resultDatas[6], resultDatas[7]});
                }

                //包长度
                int dataLen = (resultDatas[5] << 8 & 0xFF00) | (resultDatas[4] & 0xFF);
                Logger.i("【包长度】" + dataLen);
                //报文的长度比实际数据长度短，则应该再接收一次包
                if (dataLen + 9 < resultDatas.length) {
                    return new Respond(ERROR_LEN).setCmd(new byte[]{resultDatas[6], resultDatas[7]});
                }

                final byte verify = verify(resultDatas, 2, 3);
                Logger.i("【校验值】" + verify + "," + resultDatas[resultDatas.length - 3]);
                if (verify != resultDatas[resultDatas.length - 3]) {
                    return new Respond(RespondStatus.ERROR_VERIFY).setCmd(new byte[]{resultDatas[6], resultDatas[7]});
                }

                final byte type = (byte) (resultDatas[2] & 0xFF);
                if (type == RESPOND) {
                    final boolean cmdCheck = (((cmd[0] & 0xFF) == (resultDatas[6] & 0xFF)) && ((cmd[1] & 0xFF) == (resultDatas[7] & 0xFF)));//同一条指令的响应
                    if (cmdCheck) {
                        Logger.i("【包序号】" + resultDatas[3]);
                        if (packageId != resultDatas[3])
                            return new Respond(ERROR_PACKAGER_ID);
                        return respondHandler(true, resultDatas, dataLen, Respond.RESPOND);
                    } else {
                        return new Respond(ERROR_CMD_NO_FIT).setAllPackageData(resultDatas).setCmd(new byte[]{resultDatas[6], resultDatas[7]});
                    }
                } else if (type == REQ) {
                    //命令
                    byte[] params = new byte[dataLen];
                    System.arraycopy(resultDatas, 6, params, 0, dataLen);
                    Logger.i(params, Logger.TYPE.CODE16);
                    return new Respond(RespondStatus.OK, Respond.NOTICE, params).setAllPackageData(resultDatas).setCmd(new byte[]{resultDatas[6], resultDatas[7]});
                } else {
                    return respondHandler(false, resultDatas, dataLen, corverType(type));
                }
            }
            return new Respond(RespondStatus.ERROR_HEAD_FORMAT).setCmd(new byte[]{resultDatas[6], resultDatas[7]});
        }

        private Respond respondHandler(boolean suc, byte[] resultDatas, int dataLen, int type) {
            Logger.i(" == respondHandler ==");
            Logger.i(resultDatas, Logger.TYPE.CODE16);
            if (suc) {
                dataLen -= 2;
                if (dataLen > 0) {
                    byte[] buffer = new byte[dataLen];
                    System.arraycopy(resultDatas, 8, buffer, 0, dataLen);
                    Logger.i(buffer, Logger.TYPE.CODE16);
                    return new Respond(OK, type, buffer).setAllPackageData(resultDatas).setCmd(new byte[]{resultDatas[6], resultDatas[7]});
                } else {
                    return new Respond(OK, type).setAllPackageData(resultDatas).setCmd(new byte[]{resultDatas[6], resultDatas[7]});
                }
            } else {
                return new Respond(RespondStatus.valueOf(resultDatas[8], 1), type).setAllPackageData(resultDatas).setCmd(new byte[]{resultDatas[6], resultDatas[7]});
            }
        }
    };

    //包头
    public abstract byte[] header();

    //包尾（结束符）
    public abstract byte[] laster();

    //从第3个字节到包长度的数据长度
    public abstract int getPackagePreLength();

    // 数据长度
    public abstract int getPackageLength(byte[] data);

    public abstract int corverType(int type);

    /**
     * 请求报文组包
     *
     * @param packageId 包序号
     * @param cmd       指令
     * @param params    指令参数
     * @return
     */
    public abstract byte[] requestPack(byte packageId, byte[] cmd, byte[] params) throws IOException;

    public abstract Respond respondPack(byte packageId, byte[] resultDatas, byte[] cmd);

    /**
     * 校验和,从包类型到参数的逐自己异或结果
     *
     * @param params
     * @param leftOffset
     * @param rightOffset
     * @return
     */
    public byte verify(byte[] params, int leftOffset, int rightOffset) {
        final int len = params.length - rightOffset;
        byte result = params[leftOffset];
        for (int i = leftOffset + 1; i < len; i++) {
            result = (byte) (params[i] ^ result);
        }
        return result;
    }

    /**
     * 读取数据验证
     *
     * @param in
     * @return
     * @throws IOException
     */
    public byte[] read(InputStream in) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        boolean topBoolean = true;
        //起始符验证
        do {
            byte[] headBytes = header();
            final int headLen = headBytes.length;
            byte[] topBytes = read_fixed_bytes(in, headLen);
            for (int i = 0; i < headLen; i++) {
                if ((headBytes[i] & 0xFF) == (topBytes[i] & 0xFF)) {
                    topBoolean = topBoolean && true;
                } else {
                    topBoolean = topBoolean && false;
                    break;
                }
            }
            Logger.i("【读取报文头】" + topBoolean);
            if (topBoolean) {
                bos.reset();
                bos.write(topBytes);
                break;
            }
        } while (true);

        //报文长度
        final int prePackageLen = getPackagePreLength();
        byte[] lenData = read_fixed_bytes(in, prePackageLen);
        bos.write(lenData);
        final int packageLen = getPackageLength(lenData);
        Logger.i("【报文长度】" + packageLen);
        byte[] realPackage = read_fixed_bytes(in, packageLen);
        Logger.i("【读取报文长度】");
        Logger.i(realPackage, Logger.TYPE.CODE16);
        byte[] lasterBytes = laster();
        //报文结束符
        if (lasterBytes.length == 1 && (realPackage[realPackage.length - 2] & 0xFF) == (lasterBytes[0] & 0xFF)) {
            bos.write(realPackage);
        } else {
            boolean lasterBoolean = true;
            final int realPackageLen = realPackage.length;
            final int lasterLen = lasterBytes.length;
            for (int i = lasterLen - 1, j = realPackageLen - 1; i >= 0; i--, j--) {
                if ((realPackage[j] & 0xFF) == (lasterBytes[i] & 0xFF)) {
                    lasterBoolean = lasterBoolean && true;
                } else {
                    lasterBoolean = lasterBoolean && false;
                    break;
                }
            }
            if (!lasterBoolean) {
                bos.reset();
            } else {
                bos.write(realPackage);
            }
        }
        if (bos.size() > 0) {
            byte[] datas = bos.toByteArray();
            Logger.i("【接收到报文数据】");
            Logger.i(datas, Logger.TYPE.CODE16);
            bos.close();
            return datas;
        }
        return null;
    }

    /**
     * @param in
     * @param len
     * @return
     * @throws IOException
     */
    private byte[] read_fixed_bytes(InputStream in, int len) throws IOException {
        byte[] result = new byte[len];
        byte[] data = new byte[len];
        int lens;
        int data_pos = 0;
        while (true) {
            if (len != 0) {
                lens = in.read(data);
                len = len - lens;
                System.arraycopy(data, 0, result, data_pos, lens);
                data_pos += lens;
                data = new byte[len];
            } else {
                break;
            }
        }
        return result;
    }
}
